//1/12/2004 - Daniel Moth creates BackgroundWorker (http://www.danielmoth.com/Blog/) 
//4/12/2004 - DDGM: Changed text in comments, and exception messages. 
// Made RunWorkerAsync throw if no eventhandler attached 
// Added ctor to make the class work on the full framework as is 

namespace System.ComponentModel
{

    #region "EventArgs classes"
    public class RunWorkerCompletedEventArgs : System.EventArgs
    {
        // This class should inherit from AsyncCompletedEventArgs but I don't see the point in the CF's case 

        private readonly object mResult;
        private readonly bool mCancelled;
        private readonly System.Exception mError;

        public RunWorkerCompletedEventArgs(object aResult, System.Exception aError, bool aCancelled)
        {
            mResult = aResult;
            mError = aError;
            mCancelled = aCancelled;
        }

        public object Result
        {
            get { return mResult; }
        }

        public bool Cancelled
        {
            get { return mCancelled; }
        }

        public System.Exception Error
        {
            get { return mError; }
        }
    }

    public class ProgressChangedEventArgs : System.EventArgs
    {

        private readonly Int32 mProgressPercent;
        private readonly object mUserState;

        public ProgressChangedEventArgs(Int32 aProgressPercent, object aUserState)
        {
            mProgressPercent = aProgressPercent;
            mUserState = aUserState;
        }

        public Int32 ProgressPercentage
        {
            get { return mProgressPercent; }
        }

        public object UserState
        {
            get { return mUserState; }
        }
    }

    public class DoWorkEventArgs : System.ComponentModel.CancelEventArgs
    {

        private readonly object mArgument;
        private object mResult;

        public DoWorkEventArgs(object aArgument)
        {
            mArgument = aArgument;
        }

        public object Argument
        {
            get { return mArgument; }
        }

        public object Result
        {
            get { return mResult; }
            set { mResult = value; }
        }
    }
    #endregion

    #region "Delegates for 3 events of class"
    public delegate void DoWorkEventHandler(object sender, DoWorkEventArgs e);
    public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
    public delegate void RunWorkerCompletedEventHandler(object sender, RunWorkerCompletedEventArgs e);
    #endregion

    #region "BackgroundWorker Class"
    /// <summary> 
    /// Executes an operation on a separate thread. 
    /// </summary> 
    public class BackgroundWorker : System.ComponentModel.Component
    {

        #region "Public Interface"
        public event DoWorkEventHandler DoWork;
        public event ProgressChangedEventHandler ProgressChanged;
        public event RunWorkerCompletedEventHandler RunWorkerCompleted;

        /// <summary> 
        /// Initializes a new instance of the BackgroundWorker class. 
        /// </summary> 
        public BackgroundWorker()
            : this(new System.Windows.Forms.Control())
        {
            // ideally we want to call Control.CreateControl() 
            // without it, running on the desktop will crash (it is OK on the CF) 
            // [on the full fx simply calling a control's constructor does not create the Handle.] 

            // The CreateControl method is not supported on the CF so to keep this assembly retargettable 
            // I have offered the alternative ctor for desktop clients 
            // (where they can pass in already created controls) 
        }

        /// <summary> 
        /// Initializes a new instance of the BackgroundWorker class. 
        /// Call from the desktop code as the other ctor is not good enough 
        /// Call it passing in a created control e.g. the Form 
        /// </summary> 
        public BackgroundWorker(System.Windows.Forms.Control aControl)
            : base()
        {
            mGuiMarshaller = aControl;
        }

        /// <summary> 
        /// Gets a value indicating whether the application has requested cancellation of a background operation. 
        /// </summary> 
        public bool CancellationPending
        {
            get { return mCancelPending; }
        }

        /// <summary> 
        /// Gets a value indicating whether the backgroundworker is running. 
        /// </summary> 
        public bool IsBusy
        {
            get { return mInUse; }
        }

        /// <summary> 
        /// Raises the BackgroundWorker.ProgressChanged event. 
        /// </summary> 
        /// <param name="aProgressPercent">The percentage, from 0 to 100, of the background operation that is complete. </param> 
        public void ReportProgress(Int32 aProgressPercent)
        {
            this.ReportProgress(aProgressPercent, null);
        }

        /// <summary> 
        /// Raises the BackgroundWorker.ProgressChanged event. 
        /// </summary> 
        /// <param name="aProgressPercent">The percentage, from 0 to 100, of the background operation that is complete. </param> 
        /// <param name="aUserState">The state object passed to BackgroundWorker.RunWorkerAsync(System.Object).</param> 
        public void ReportProgress(Int32 aProgressPercent, object aUserState)
        {
            if (!mDoesProgress)
            {
                throw new System.InvalidOperationException("Doesn't do progress events. You must WorkerReportsProgress=True");
            }

            // Send the event to the GUI 
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(ProgressHelper), new ProgressChangedEventArgs(aProgressPercent, aUserState));
        }

        /// <summary> 
        /// Starts execution of a background operation. 
        /// </summary> 
        public void RunWorkerAsync()
        {
            this.RunWorkerAsync(null);
        }

        /// <summary> 
        /// Starts execution of a background operation. 
        /// </summary> 
        /// <param name="aArgument"> A parameter for use by the background operation to be executed in the BackgroundWorker.DoWork event handler.</param> 
        public void RunWorkerAsync(object aArgument)
        {
            if (mInUse)
            {
                throw new System.InvalidOperationException("Already in use.");
            }

            if (DoWork == null)
            {
                throw new System.InvalidOperationException("You must subscribe to the DoWork event.");
            }

            mInUse = true;
            mCancelPending = false;

            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(DoTheRealWork), aArgument);
        }

        /// <summary> 
        /// Requests cancellation of a pending background operation. 
        /// </summary> 
        public void CancelAsync()
        {
            if (!mDoesCancel)
            {
                throw new System.InvalidOperationException("Does not support cancel. You must WorkerSupportsCancellation=True");
            }

            mCancelPending = true;
        }

        /// <summary> 
        /// Gets or sets a value indicating whether the BackgroundWorker object can report progress updates. 
        /// </summary> 
        public bool WorkerReportsProgress
        {
            get { return mDoesProgress; }
            set { mDoesProgress = value; }
        }

        /// <summary> 
        /// Gets or sets a value indicating whether the BackgroundWorker object supports asynchronous cancellation. 
        /// </summary> 
        public bool WorkerSupportsCancellation
        {
            get { return mDoesCancel; }
            set { mDoesCancel = value; }
        }
        #endregion

        #region "Fields"
        //Ensures the component is used only once per session 
        private bool mInUse;

        //Stores the cancelation request that the worker thread (user's code) should check via CancellationPending 
        private bool mCancelPending;

        //Whether the object supports cancelling or not (and progress or not) 
        private bool mDoesCancel;
        private bool mDoesProgress;

        //Helper objects since Control.Invoke takes no arguments 
        private RunWorkerCompletedEventArgs mFinalResult;
        private ProgressChangedEventArgs mProgressArgs;

        // Helper for marshalling execution to GUI thread 
        private System.Windows.Forms.Control mGuiMarshaller;
        #endregion

        #region "Private Methods"
        // Async(ThreadPool) called by ReportProgress for reporting progress 
        private void ProgressHelper(object o)
        {
            mProgressArgs = (ProgressChangedEventArgs)o;
            mGuiMarshaller.Invoke(new System.EventHandler(TellThemOnGuiProgress));
        }

        // ControlInvoked by ProgressHelper for raising progress 
        private void TellThemOnGuiProgress(object sender, System.EventArgs e)
        {
            if (ProgressChanged != null)
            {
                ProgressChanged(this, mProgressArgs);
            }
        }
        // Async(ThreadPool) called by RunWorkerAsync [the little engine of this class] 
        private void DoTheRealWork(object o)
        {
            // declare the vars we will pass back to client on completion 
            System.Exception er = null;
            bool ca = false;
            object result = null;

            // Raise the event passing the original argument and catching any exceptions 
            try
            {
                DoWorkEventArgs inOut = new DoWorkEventArgs(o);
                if (DoWork != null)
                {
                    DoWork(this, inOut);
                }
                ca = inOut.Cancel;
                result = inOut.Result;
            }
            catch (System.Exception ex)
            {
                er = ex;
            }

            // store the completed final result in a temp var 
            RunWorkerCompletedEventArgs tempResult = new RunWorkerCompletedEventArgs(result, er, ca);

            // return execution to client by going async here 
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(RealWorkHelper), tempResult);

            // prepare for next use 
            mInUse = false;
            mCancelPending = false;
        }

        // Async(ThreadPool) called by DoTheRealWork [to avoid any rentrancy issues at the client end] 
        private void RealWorkHelper(object o)
        {
            mFinalResult = (RunWorkerCompletedEventArgs)o;
            mGuiMarshaller.Invoke(new System.EventHandler(TellThemOnGuiCompleted));
        }

        // ControlInvoked by RealWorkHelper for raising final completed event 
        private void TellThemOnGuiCompleted(object sender, System.EventArgs e)
        {
            if (RunWorkerCompleted != null)
            {
                RunWorkerCompleted(this, mFinalResult);
            }
        }
        #endregion
    }
    #endregion

}